/*
		* Author:	 Sander
		* Date:		 12-apr-2011
		* Name:		 Ray
		* Version:	1.0
		*
		* This code is property of Sander blahblahblah copyright blahblah.
*/


package javaraytracer;

import javaraytracer.Objects.Light;
import java.util.ArrayList;
import javaraytracer.Materials.Material;
import javaraytracer.Objects.Traceable;

//PENDING:add function/property description
public class Ray
{
	
	public Vec3 origin;
	public Vec3 direction;

	private ArrayList scene;
	private Camera cam;
	private Vec3 spec;

	public Ray( Vec3 o, Vec3 d )
	{
		scene = JavaRayTracer.scene;
		cam = JavaRayTracer.instance.cam;
		origin = new Vec3( o );
		direction = new Vec3( d );
		spec = new Vec3();
	}

	public Vec3 trace( Traceable currentObject, int maxReflectionsLeft )
	{
		IntersectionInfo nearestHit = null;

		for(int i = 0; i < scene.size(); i++)
		{
			Traceable t = (Traceable)(scene.get(i));
			if( t != currentObject )
			{
				//trace any object
				IntersectionInfo info = t.intersect(this);

				if( info.hit && (nearestHit == null || info.distance < nearestHit.distance) && (info.location.sub(cam.origin)).length() < cam.farPlane )
				{
					if(t instanceof javaraytracer.Objects.OBJModel && info.hit == true)
					{
						//System.out.println("ja");
					}
					nearestHit = info;
				}
			}
		}
		if( nearestHit != null )
		{
			Material mat = nearestHit.object.getMaterial();

			Vec3 col = mat.colour.mul( mat.ambient.add(JavaRayTracer.globalAmbient) );
			for(int i = 0; i < scene.size(); i++)
			{
				if(scene.get(i) instanceof Light)
				{
					//trace light
					Light light = (Light)scene.get(i);
					if(mat.drawShadow == true)
					{
						Vec3 shadow = Vec3.EMPTY;
						Vec3 shadowFeelerDirection = light.location.sub(nearestHit.location);
						Ray shadowFeeler = new Ray(nearestHit.location, shadowFeelerDirection);
						if(!shadowFeeler.hit(nearestHit.object) ) //TODO: make soft shadow with multisampling.
						{
							//calculate color on light
							shadow = shadow.add( localLight(nearestHit, light) );
						}
						for(int s = 0; s < JavaRayTracer.softshadowIts; s++)
						{
							shadowFeelerDirection = light.location.add(Vec3.randomVec3(-0.2, 0.2)).sub(nearestHit.location);
							shadowFeeler = new Ray(nearestHit.location, shadowFeelerDirection);
							if(!shadowFeeler.hit(nearestHit.object) )
							{
								//calculate color on light
								shadow = shadow.add( localLight(nearestHit, light) );
							}
						}
						col = col.add(shadow.div(JavaRayTracer.softshadowIts + 1));

					}
					else
					{
						//calculate color on light
						col = col.add( localLight(nearestHit, light) );
					}
				}
			}

			//calculate reflection
			Vec3 reflect_color = new Vec3();

			if(maxReflectionsLeft > 0 && mat.reflectance > 0)
			{
				Vec3 reflect_d = this.direction.sub( nearestHit.normal.mul( 2.0 * (nearestHit.normal.dot( this.direction )) ) );
				Vec3 reflect_o = nearestHit.location;

				Ray reflection = new Ray( reflect_o, reflect_d );

				reflect_color = reflection.trace( nearestHit.object, maxReflectionsLeft - 1 );
				reflect_color = reflect_color.mul( mat.reflectance );
			}

			//add the reflection
			Vec3 sum = col.mul( 1 - mat.reflectance ).add(reflect_color.mul( mat.colour ) );
			sum = sum.add( spec );

			return sum;
		}
		else
		{
			//return background color
			return new Vec3( 0.435f, 0.89f, 0.89f );
		}
	}

	public Vec3 localLight( IntersectionInfo info, Light light )
	{
		Vec3 coll			= new Vec3();
		Vec3 pixelNormal	= info.normal;
		Vec3 toLight		= light.location.sub( info.location );
		double toLightLength	= toLight.length();
		Vec3 lightUnit		= toLight.normalized();
		Vec3 back			= cam.origin.sub( info.location ).normalized();
		Material mat		= info.object.getMaterial(info);

		double intensity = lightUnit.dot( pixelNormal );
		if(mat.specularN > 0)
		{
			spec = spec.add( light.col.mul( mat.specular.mul(Math.pow( back.add(lightUnit).div( back.add(lightUnit).length() ).dot( info.normal ), mat.specularN + 100 * mat.reflectance) ) ) );
		}

		if(intensity > 0)
		{
			double lightDistance = (light.distance - toLightLength) / light.distance;
			coll = mat.colour.mul( light.col.mul( light.intensity ) ).mul( intensity * (lightDistance > 0 ? lightDistance : 0) );
		}

		return coll;
	}


	public boolean hit( Traceable ignoreObject )
	{
		for(int i = 0; i < scene.size(); i++)
		{
			Traceable t = (Traceable)scene.get(i);
			if(t != ignoreObject && t != ignoreObject.parent && t.getMaterial().castShadow == true)
			{
				if(t.hit( this ))
				{
					return true;
				}
			}
		}
		return false;
	}
}
